package com.dianping.localcache;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.concurrent.atomic.AtomicLong;

import android.content.Context;
import android.os.Handler;
import android.os.Message;

import com.dennytech.tac.utils.Daemon;
import com.dennytech.tac.utils.Log;

public class FileCacheManager implements CacheManager<byte[]> {
	private static final String TAG = FileCacheManager.class.getSimpleName();

	private Context mContext;
	private File mFCacheDir;
	private long mSizeLimit;
	public FileCacheManager(Context context, String cacheFolder, long sizeLimit) {
		mContext = context;
		mSizeLimit = sizeLimit;

		if (mContext != null) {
			File cacheDir = mContext.getCacheDir();
			mFCacheDir = new File(cacheDir, cacheFolder);
			mFCacheDir.mkdir();
		}
	}

	private String getFileName(String url) {
		if ((url == null) || (url.length() <= 0)) {
			return null;
		}

		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < url.length(); ++i) {
			char c = url.charAt(i);
			if (Character.isLetter(c) || Character.isDigit(c)) {
				sb.append(c);
			} else {
				sb.append('_');
			}
		}

		return sb.toString();
	}

	@Override
	public byte[] get(String url) {
		String fileName = getFileName(url);
		if (fileName == null) {
			Log.e(TAG, "get fail because of null filename " + url);
			return null;
		}
		if (mFCacheDir == null) {
			Log.e(TAG, "get fail because of null mFCache");
			return null;
		}

		Log.i(TAG, "get " + fileName);
		File f = new File(mFCacheDir, fileName);
		if (!f.exists()) {
			Log.i(TAG, "get fail because file not exist");
			return null;
		}

		try {
			FileInputStream in = new FileInputStream(f);
			int avaiable = in.available();
			byte[] bytes = new byte[avaiable];
			in.read(bytes);
			in.close();

			// do not call this now, because it will slow down the program
			// f.setLastModified(System.currentTimeMillis());

			Log.i(TAG, "get succeed");
			return bytes;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		Log.e(TAG, "get fail because read file fail");
		return null;
	}

	@Override
	public boolean put(String url, byte[] val) {
		String fileName = getFileName(url);
		if (fileName == null) {
			Log.e(TAG, "put fail because of null filename " + url);
			return false;
		}
		if (mFCacheDir == null) {
			Log.e(TAG, "put fail because of null mFCache");
			return false;
		}

		Log.i(TAG, "put " + fileName);
		File f = new File(mFCacheDir, fileName);
		if (f.exists()) {
			Log.e(TAG, "put remove old first");
			f.delete();
		}
		try {
			if (!f.createNewFile()) {
				Log.e(TAG, "put fail because new file fail");
				return false;
			}
		} catch (IOException e) {
			e.printStackTrace();
			Log.e(TAG, "put fail because new file exception");
			return false;
		}
		try {
			FileOutputStream out = new FileOutputStream(f);
			out.write(val);
			out.close();

			if (mCurTotalSize.get() > 0) { // > 0 means already initialized by list file sizes
				mCurTotalSize.addAndGet(f.length());

				if (mSizeLimit < mCurTotalSize.get()) {
					mRecycleHandler.removeMessages(CMD_RECYCLE);
					mRecycleHandler.sendEmptyMessageDelayed(CMD_RECYCLE, 1000);
				}
			}

			Log.i(TAG, "put succeed");
			return true;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		if (f.exists()) {
			f.delete();
		}

		Log.e(TAG, "put fail because write file fail");
		return false;
	}

	@Override
	public boolean remove(String url) {
		String fileName = getFileName(url);
		if (fileName == null) {
			Log.e(TAG, "remove fail because of null filename " + url);
			return false;
		}
		if (mFCacheDir == null) {
			Log.e(TAG, "remove fail because of null mFCache");
			return false;
		}

		Log.i(TAG, "remove " + fileName);
		File f = new File(mFCacheDir, fileName);
		return doRemove(f);
	}

	private boolean doRemove(File f) {
		if (f == null) {
			return false;
		}

		if (f.exists()) {
			if (mCurTotalSize.get() > 0) { // > 0 means already initialized by list file sizes
				mCurTotalSize.addAndGet(-f.length());
			}
			f.delete();
		}

		Log.e(TAG, "doRemove succeed " + f.getName());
		return true;
	}

	@Override
	public void close() {
	}

	public void clear() {
		mFCacheDir.delete();
		mFCacheDir.mkdir();
	}

	private RecycleHandler mRecycleHandler = new RecycleHandler();

	private static final int CMD_RECYCLE = 1;
	private class RecycleHandler extends Handler {
		public RecycleHandler() {
			super(Daemon.looper());
		}

		@Override
		public void handleMessage(Message msg) {
			Log.e(TAG, "RecycleHandler " + msg);
			if (msg == null) {
				return;
			}

			if (msg.what == CMD_RECYCLE) {
				doRecycle();
			}
		}
	}

	private AtomicLong mCurTotalSize = new AtomicLong();

	private long getTotalSize() {
		long totalSize = 0;
	    File[] fileList = mFCacheDir.listFiles();

	    for(int i = 0; i < fileList.length; i++) {
	        if (fileList[i].isDirectory()) {
	            continue;
	        } else {
	        	totalSize += fileList[i].length();
	        }
	    }

	    return totalSize;
	}

	private void doRecycle() {
		if (mFCacheDir == null) {
			Log.e(TAG, "doRecycle fail because of null mFCache");
			return;
		}

		if (mCurTotalSize.get() == 0) {
			mCurTotalSize.addAndGet(getTotalSize());
		}

	    Log.i(TAG, "doRecycle limit = " + mSizeLimit + ", cur = " + mCurTotalSize);
	    if (mCurTotalSize.get() < mSizeLimit) {
	    	Log.i(TAG, "doRecycle return because not exceed");
	    	return;
	    }

	    File[] files = mFCacheDir.listFiles();
	    if ((files == null) || (files.length <= 0)) {
	    	Log.e(TAG, "doRecycle return because files empty");
	    	return;
	    }
		Arrays.sort(files, new Comparator<File>() {
			public int compare(File f1, File f2) {
				return (int) (f1.lastModified() - f2.lastModified());
			}
		});

		int index = 0;
		while ((mCurTotalSize.get() > mSizeLimit * 0.85) && (index < files.length)) {
			doRemove(files[index++]);
		}

		Log.i(TAG, "doRecycle finished");
	}
}
